package jnausb;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.PointerType;
import com.sun.jna.ptr.PointerByReference;

import java.util.logging.Logger;

/**
 *
 */
public interface LibUSB
    extends Library
{
  public final static int LIBUSB_SUCCESS = 0;

  /**
   * Input/output error
   */
  public final static int LIBUSB_ERROR_IO = -1;

  /**
   * Invalid parameter
   */
  public final static int LIBUSB_ERROR_INVALID_PARAM = -2;

  /**
   * Access denied (insufficient permissions)
   */
  public final static int LIBUSB_ERROR_ACCESS = -3;

  /**
   * No such device (it may have been disconnected)
   */
  public final static int LIBUSB_ERROR_NO_DEVICE = -4;

  /**
   * Entity not found
   */
  public final static int LIBUSB_ERROR_NOT_FOUND = -5;

  /**
   * Resource busy
   */
  public final static int LIBUSB_ERROR_BUSY = -6;

  /**
   * Operation timed out
   */
  public final static int LIBUSB_ERROR_TIMEOUT = -7;

  /**
   * Overflow
   */
  public final static int LIBUSB_ERROR_OVERFLOW = -8;

  /**
   * Pipe error
   */
  public final static int LIBUSB_ERROR_PIPE = -9;

  /**
   * System call interrupted (perhaps due to signal)
   */
  public final static int LIBUSB_ERROR_INTERRUPTED = -10;

  /**
   * Insufficient memory
   */
  public final static int LIBUSB_ERROR_NO_MEM = -11;

  /**
   * Operation not supported or unimplemented on this platform
   */
  public final static int LIBUSB_ERROR_NOT_SUPPORTED = -12;

  /**
   * Other error
   */
  public final static int LIBUSB_ERROR_OTHER = -99;

//  LibUSB libUSB = (LibUSB) Native.loadLibrary("usb-1.0", LibUSB.class);
  Logger logger = Logger.getLogger(LibUSB.class.getName());

  int libusb_init(PointerByReference context);

  int libusb_init(PointerByReference context[]);

  int libusb_get_device_list(Context ctx, PointerByReference deviceList);

  int libusb_get_device_descriptor(Device device, DeviceDescriptor descriptor);

  int libusb_open(Device device, PointerByReference handle);

  int libusb_open(Device device, DeviceHandle handle);

  int libusb_close(DeviceHandle handle);

  /**
   * Set message verbosity.
   * <p/>
   * <ul><li>Level 0: no messages ever printed by the library (default) Level 1: error messages are printed to stderr
   * <li>Level 2: warning and error messages are printed to stderr <li>Level 3: informational messages are printed to
   * stdout, warning and error messages are printed to stderr <p/>>The default level is 0, which means no messages are
   * ever printed. If you choose to increase the message verbosity level, ensure that your application does not close
   * the stdout/stderr file descriptors.
   * <p/>
   * You are advised to set level 3. libusb is conservative with its message logging and most of the time, will only log
   * messages that explain error conditions and other oddities. This will help you debug your software.
   * <p/>
   * If the LIBUSB_DEBUG environment variable was set when libusb was initialized, this function does nothing: the
   * message verbosity is fixed to the value in the environment variable.
   * <p/>
   * If libusb was compiled without any message logging, this function does nothing: you'll never get any messages.
   * <p/>
   * If libusb was compiled with verbose debug message logging, this function does nothing: you'll always get messages
   * from all levels.
   *
   * @param ctx   the context to operate on
   * @param level debug level to set
   */
  void libusb_set_debug(Context ctx, int level);

  /**
   * Perform a USB control transfer.  The The direction of the transfer is inferred from the bmRequestType field of the
   * setup packet.
   *
   * @param dev_handle    a handle for the device to communicate with
   * @param bmRequestType the request type field for the setup packet
   * @param bRequest      the request field for the setup packet
   * @param wValue        the value field for the setup packet
   * @param wIndex        the index field for the setup packet
   * @param data          a suitably-sized data buffer for either input or output (depending on direction bits within
   *                      bmRequestType)
   * @param wLength       the length field for the setup packet. The data buffer should be at least this size.
   * @param timeout       timeout (in milliseconds) that this function should wait before giving up due to no response
   *                      being received. For an unlimited timeout, use value 0.
   *
   * @return
   */
  int libusb_control_transfer(DeviceHandle dev_handle, short bmRequestType, short bRequest, int wValue, int wIndex,
                              String data, int wLength, int timeout);

  void libusb_exit(Context context);

  public static class Util
  {
    public static LibUSB libUSB;

    public static Context initialize()
    {
      //System.loadLibrary("libusb-1.0"); // this kickstarts it for JNA. Use java.library.path to point to the dll
      libUSB = (LibUSB) Native.loadLibrary("usb-1.0", LibUSB.class);
      PointerByReference ptr = new PointerByReference();
      int returnCode = libUSB.libusb_init(ptr);
      if (returnCode != 0)
      {
        logger.warning("initialize returned " + returnCode);
        return null;
      }
      return new Context(ptr.getValue());
    }

    public static Device[] getDeviceList(Context ctx)
    {
      PointerByReference deviceList = new PointerByReference();
      int cnt = libUSB.libusb_get_device_list(ctx, deviceList);
      Pointer[] pointerArray = deviceList.getValue().getPointerArray(0, cnt);
      Device[] realList = new Device[cnt];
      for (int i = 0; i < cnt; ++i)
        realList[i] = new Device(pointerArray[i]);
      return realList;
    }

    public static DeviceDescriptor getDeviceDescriptor(Device device)
    {
      DeviceDescriptor desc = new DeviceDescriptor();
      libUSB.libusb_get_device_descriptor(device, desc);
      return desc;
    }

    public static DeviceHandle getDeviceHandle(Device device)
    {
      PointerByReference deviceHandleRef = new PointerByReference();
      int ret = libUSB.libusb_open(device, deviceHandleRef);
      if (ret == LIBUSB_ERROR_NOT_SUPPORTED)
        return null;
      DeviceHandle deviceHandle = new DeviceHandle(deviceHandleRef.getValue());
      return deviceHandle;
    }
  }

  public static class Context
      extends com.sun.jna.PointerType
  {
    public Context()  // empty constructor required
    {
    }

    public Context(com.sun.jna.Pointer pointer)
    {
      super(pointer);
    }
  }

  public static class Device
      extends PointerType
  {
    public Device() // empty constructor required
    {
    }

    public Device(Pointer pointer)
    {
      super(pointer);
    }
  }

  public static class DeviceHandle
      extends PointerType
  {
    public DeviceHandle() // empty constructor required
    {
    }

    public DeviceHandle(Pointer pointer)
    {
      super(pointer);
    }
  }
}
